using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Oxage.Common;
using Oxage.Midi;

namespace Oxage.Forms
{
	public partial class RedirectForm : Form
	{
		#region Members
		private bool isOpened = false;
		private IConfigPanel inputConfigPanel = null;
		private IConfigPanel outputConfigPanel = null;
		private PluginManager<IConnector> man = null;
		#endregion

		#region Constructor
		public RedirectForm()
		{
			InitializeComponent();

			LoadPlugins();
			LoadInputs();
			LoadOutputs();
			LoadProfiles();
		}
		#endregion

		#region Properties
		public bool IsOpened
		{
			get
			{
				return this.isOpened;
			}
			set
			{
				this.isOpened = value;
				RefreshControls();
			}
		}

		public IFilter Filter
		{
			get;
			set;
		}

		public IConnector Input
		{
			get
			{
				return cmbInput.SelectedItem as IConnector;
			}
		}

		public IConnector Output
		{
			get
			{
				return cmbOutput.SelectedItem as IConnector;
			}
		}

		public string SelectedProfileName
		{
			get
			{
				return cmbProfile.SelectedItem as string ?? "Default";
			}
			set
			{
				if (value != null)
				{
					foreach (var item in cmbProfile.Items)
					{
						if (item.ToString() == value.ToString())
						{
							cmbProfile.SelectedItem = item;
							break;
						}
					}
				}
			}
		}

		public int OctaveShift
		{
			get
			{
				int shift = cmbOctaveShift.SelectedIndex - 3;
				//int.TryParse(cmbOctaveShift.Text, out shift);
				return shift;
			}
		}
		#endregion

		#region Methods
		protected void LoadPlugins()
		{
			//Loadable connectors
			man = new PluginManager<IConnector>();
			man.PluginFound += delegate(object sender, EventArgs<Plugin<IConnector>> e)
			{
				Plugin<IConnector> plugin = e.Result;
				if (plugin != null && plugin.Type != null)
				{
					cmbInput.Items.Add(plugin.CreateInstance());
					cmbOutput.Items.Add(plugin.CreateInstance());
				}
			};
			man.PluginBasePath = AppConfig.PluginBasePath;
			man.Logger = new Logger();
			man.LoadPluginsAsync();
			man.AutoLoad = true;
		}

		protected void LoadInputs()
		{
			cmbInput.Items.Clear();

			List<IConnector> connectors = new List<IConnector>();

			//Embedded connectors
			connectors.Add(new SerialPortConnector());

			try
			{
				connectors.Add(new DirectMidiConnector());
			}
			catch
			{
				//Issues with loading in Windows 7
			}

			connectors.Add(new UdpConnector());

			connectors.Add(new ToubMidiConnector());
			connectors.Add(new SanfordMidiConnector());

			foreach (IConnector connector in connectors)
			{
				//Attach only connectors that can work as input
				cmbInput.Items.Add(connector);
			}

			//Select the first one
			if (cmbInput.Items.Count > 0)
			{
				cmbInput.SelectedIndex = 0;
			}
		}

		protected void LoadOutputs()
		{
			cmbOutput.Items.Clear();

			List<IConnector> connectors = new List<IConnector>();

			//Embedded connectors
			connectors.Add(new SerialPortConnector());

			try
			{
				connectors.Add(new DirectMidiConnector());
			}
			catch
			{
				//Issues with loading in Windows 7
			}

			connectors.Add(new UdpConnector());

			connectors.Add(new ToubMidiConnector());
			connectors.Add(new SanfordMidiConnector());

			foreach (IConnector connector in connectors)
			{
				//Attach only connectors that can work as output
				cmbOutput.Items.Add(connector);
			}

			//Select the first one
			if (cmbOutput.Items.Count > 0)
			{
				cmbOutput.SelectedIndex = 0;
			}
		}

		protected void LoadConfigPanel(GroupBox groupbox, IConfigPanel panel)
		{
			//Detach previous panel
			if (groupbox.Controls.Count > 1)
			{
				groupbox.Controls.RemoveAt(1);
			}

			if (panel != null)
			{
				UserControl ui = panel.GetUserControl();
				if (ui != null)
				{
					//Attach new panel
					ui.Location = new Point(9, 45);
					groupbox.Height = ui.Height + ui.Location.Y + 5;
					groupbox.Controls.Add(ui);
				}
			}

			//Align heights - apply max. height for both panels
			if (gbInput.Height < gbOutput.Height)
			{
				gbInput.Height = gbOutput.Height;
			}
			else
			{
				gbOutput.Height = gbInput.Height;
			}

			if (gbOutput.Height > groupbox.Height)
			{
				gbInput.Height = groupbox.Height;
				gbOutput.Height = groupbox.Height;
			}

			//Set the total height
			this.Height = groupbox.Height + 148;
		}

		protected void LoadProfiles()
		{
			//Load all available profiles
			IList<string> profiles = Profile.GetProfileNames();
			if (profiles == null || profiles.Count == 0)
			{
				Profile profile = new Profile();
				profile.Save("Default", "L"); //Create empty profile
				SaveProfile("Default"); //Save current values
				AppConfig.DefaultProfileName = profile.Name;
				profiles = Profile.GetProfileNames();
			}

			//Fill ComboBox
			cmbProfile.Items.Clear();
			foreach (string profile in profiles)
			{
				cmbProfile.Items.Add(profile);
			}

			//Selected the first or default one
			if (cmbProfile.Items.Count > 0)
			{
				cmbProfile.SelectedIndex = 0;
				string defaultProfileName = AppConfig.DefaultProfileName;
				if (!string.IsNullOrEmpty(defaultProfileName))
				{
					int profilesCount = cmbProfile.Items.Count;
					for (int i = 0; i < profilesCount; i++)
					{
						string profile = cmbProfile.Items[i] as string;
						if (profile == defaultProfileName)
						{
							cmbProfile.SelectedIndex = i;
							break;
						}
					}
				}
			}

			btnDelete.Enabled = (cmbProfile.Items.Count > 1);
		}

		protected void LoadProfile(string name)
		{
			Profile profile = new Profile();

			if (this.inputConfigPanel != null)
			{
				profile.Load(name, "L"); //Left panel
				this.inputConfigPanel.SetProfile(profile);
			}

			if (this.outputConfigPanel != null)
			{
				profile.Load(name, "R"); //Right panel
				this.outputConfigPanel.SetProfile(profile);
			}

			profile.Load(name, "G"); //Global

			foreach (var item in cmbInput.Items)
			{
				if (item.ToString() == profile["LeftPort"])
				{
					cmbInput.SelectedItem = item;
					break;
				}
			}

			foreach (var item in cmbOutput.Items)
			{
				if (item.ToString() == profile["RightPort"])
				{
					cmbOutput.SelectedItem = item;
					break;
				}
			}

			foreach (var item in cmbOctaveShift.Items)
			{
				if (item.ToString() == profile["OctaveShift"])
				{
					cmbOctaveShift.SelectedItem = item;
					break;
				}
			}
		}

		protected void SaveProfile()
		{
			SaveProfile(this.SelectedProfileName);
		}

		protected void SaveProfile(string name)
		{
			//Get profiles
			Profile inputProfile = (this.inputConfigPanel != null ? this.inputConfigPanel.GetProfile() : null);
			Profile outputProfile = (this.outputConfigPanel != null ? this.outputConfigPanel.GetProfile() : null);

			//Save it
			if (inputProfile != null)
			{
				inputProfile.Save(name, "L"); //Left panel
			}
			if (outputProfile != null)
			{
				outputProfile.Save(name, "R"); //Right panel
			}

			//Other inputs outside left and right panel
			Profile globalProfile = new Profile();
			if (cmbOctaveShift.SelectedItem != null)
			{
				globalProfile["OctaveShift"] = cmbOctaveShift.SelectedItem.ToString();
			}
			if (cmbInput.SelectedItem != null)
			{
				globalProfile["LeftPort"] = cmbInput.SelectedItem.ToString();
			}
			if (cmbOutput.SelectedItem != null)
			{
				globalProfile["RightPort"] = cmbOutput.SelectedItem.ToString();
			}
			globalProfile.Save(name, "G");
		}

		protected void RefreshControls()
		{
			cmbInput.Enabled = !this.isOpened;
			cmbOutput.Enabled = !this.isOpened;
			cmbProfile.Enabled = !this.isOpened;
			btnNewProfile.Enabled = !this.isOpened;
			btnSave.Enabled = !this.isOpened;
			btnDelete.Enabled = !this.isOpened;
			btnOpen.Text = (this.isOpened ? "&Disconnect" : "&Connect");
			btnDelete.Enabled = (!this.isOpened && cmbProfile.Items.Count > 1);
		}

		protected string BuildLogMessage(IMessage message, string direction)
		{
			StringBuilder builder = new StringBuilder();
			builder.AppendFormat("{0,-4}", "->");

			//Bytes to string conversion
			byte[] bytes = message.Encode();
			string raw = "";
			foreach (byte b in bytes)
			{
				raw += Convert.ToString(b, 16);
			}

			//Build a human readable message
			if (message is MidiMessage)
			{
				var midi = message as MidiMessage;
				builder.Append(raw).Append("   ");
				builder.AppendFormat("{0,-10}   ", midi.Event.ToString());
				builder.AppendFormat("Note {0,-3}   ", (int)midi.Note);
				builder.AppendFormat("Velocity {0,-3}   ", (int)midi.Velocity);
				builder.AppendFormat("Channel {0,-3}", (int)midi.Channel);
			}
			else
			{
				builder.Append(raw);
				builder.Append("unknown");
			}

			return builder.ToString();
		}
		#endregion

		#region Event Handlers
		private void Input_Received(object sender, EventArgs<IMessage> e)
		{
			this.BeginInvoke((CrossAppDomainDelegate)delegate
			{
				OnReportMessage(BuildLogMessage(e.Result, "->"));
			});
		}

		private void Output_Received(object sender, EventArgs<IMessage> e)
		{
			this.BeginInvoke((CrossAppDomainDelegate)delegate
			{
				OnReportMessage(BuildLogMessage(e.Result, "<-"));
			});
		}

		private void btnOpen_Click(object sender, EventArgs e)
		{
			if (this.IsOpened)
			{
				if (this.Filter != null)
				{
					this.Filter.Close();
				}
				this.IsOpened = false;
			}
			else
			{
				if (this.Filter != null)
				{
					this.Filter.Close();
				}
				this.Filter = new MidiFilter(this.Input, this.Output) { OctaveShift = this.OctaveShift };
				if (this.Input != null)
				{
					this.Input.Received += new EventHandler<EventArgs<IMessage>>(Input_Received);
				}
				if (this.Output != null)
				{
					this.Output.Received += new EventHandler<EventArgs<IMessage>>(Output_Received);
				}

				try
				{
					this.IsOpened = this.Filter.Open();
				}
				catch (Exception ex)
				{
					MessageBox.Show("Cannot open ports!" + Environment.NewLine + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
			}
		}

		private void btnNewProfile_Click(object sender, EventArgs e)
		{
			PromptForm form = new PromptForm();
			form.Response += delegate(object s, EventArgs<string> args)
			{
				SaveProfile(args.Result);
				LoadProfiles();
				this.SelectedProfileName = args.Result;
			};
			form.ShowDialog();
		}

		private void btnSaveProfile_Click(object sender, EventArgs e)
		{
			SaveProfile();
			RefreshControls();
		}

		private void btnDeleteProfile_Click(object sender, EventArgs e)
		{
			string profile = cmbProfile.SelectedItem as string;
			if (profile != null && Profile.Delete(profile))
			{
				int index = cmbProfile.SelectedIndex;
				cmbProfile.Items.RemoveAt(index);
				if (cmbProfile.Items.Count == 0)
				{
					//Create a new default one
					LoadProfiles();
				}
				else
				{
					//Select upper item
					cmbProfile.SelectedIndex = (index - 1 >= 0 ? index - 1 : 0);
				}
			}
			RefreshControls();
		}

		private void cmbProfile_SelectedIndexChanged(object sender, EventArgs e)
		{
			LoadProfile(this.SelectedProfileName);
			AppConfig.DefaultProfileName = this.SelectedProfileName; //Make the selected default profile, so when user open the application next time, this profile will be selected
		}

		private void cmbInput_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (this.Input != null)
			{
				IConfigPanel panel = this.Input.GetConfigPanel();
				this.inputConfigPanel = panel;
				LoadConfigPanel(gbInput, panel);

				string name = this.SelectedProfileName;
				Profile profile = new Profile();
				profile.Load(name, "L");
				panel.SetProfile(profile);
			}
		}

		private void cmbOutput_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (this.Output != null)
			{
				IConfigPanel panel = this.Output.GetConfigPanel();
				this.outputConfigPanel = panel;
				LoadConfigPanel(gbOutput, panel);

				string name = this.SelectedProfileName;
				Profile profile = new Profile();
				profile.Load(name, "R");
				panel.SetProfile(profile);
			}
		}

		private void cmbOctaveShift_SelectedIndexChanged(object sender, EventArgs e)
		{
			MidiFilter filter = this.Filter as MidiFilter;
			if (filter != null)
			{
				filter.OctaveShift = this.OctaveShift;
			}
		}
		#endregion

		#region Bubble events
		public event EventHandler<EventArgs<string>> ReportMessage;
		protected virtual void OnReportMessage(string message)
		{
			if (ReportMessage != null)
			{
				ReportMessage(this, new EventArgs<string>(message));
			}
		}
		#endregion
	}
}
